查看原文
其他

Go Gio 实战:煮蛋计时器的实现 03— 按钮

程序员ug 幽鬼 2022-09-08

争做团队核心程序员,关注「幽鬼

大家好,我是程序员幽鬼。

上篇文章介绍了空窗口的实现。今天看看如何控制窗口,比如标题、大小等。

01 目标

GUI 程序,按钮是少不了的,对按钮点击事件的响应,是必须掌握的知识点。通过本文的学习,可以掌握 GUI 事件响应的编程方法。

本节效果如下:

A button

02 主要内容

本节将介绍许多新组件,不过不会太深入细节,而是专注于程序的整体结构。虽然只是增加按钮,但需要导入不少相关新包,因此需要花点时间了解下。之后会看看如何联合 operationswidgets 来制作一个按钮。

最后,我们谈到了 Material Design[1],这是一个完善的用户界面框架(Google 出品的),也可以在 Gio 中使用。

03 需要用的到包

本节会导入如下包:

import (
  "gioui.org/app"
  "gioui.org/font/gofont"
  "gioui.org/io/system"
  "gioui.org/layout"
  "gioui.org/op"
  "gioui.org/unit"
  "gioui.org/widget"
  "gioui.org/widget/material"
)

对这些包做一个说明,其中 appunit 之前有介绍过,因此介绍其他的包。

  • font/gofont[2] - Go 自己有一套高质量的 True Type 字体,这是对其进行封装,导出给 gioui.org/text 包使用。关于官方的这套字体,可以阅读 Go 官方的博文:https://go.dev/blog/go-fonts。

  • io/system[3] - 提供从窗口发送的高级事件。最重要的是system.FrameEvent。它实际上是执行以下两项操作之一的操作列表:详细说明如何处理输入并描述要显示的内容。

  • layout[4] - 定义布局中的有用可变部分,例如尺寸约束方向。此外,它还包括称为 Flexbox[5] 的布局概念。它被广泛用于 Web 和用户界面开发,详情可以参考 Mozilla 上的介绍[6]

  • op[7] - Operations 或 ops 是 Gio 的核心。它们用于更新用户界面。有一些操作用于绘制、处理输入、更改窗口属性、缩放、旋转等。有趣的是还有[8],可以记录稍后要执行的操作。总之,这意味着操作列表是一个可变堆栈,你可以在其中控制流程。

  • widget[9] - 小部件提供 UI 组件的底层功能,例如状态跟踪和事件处理。鼠标是否悬停在按钮上?是否被点击过,如果有,点击了多少次?

  • widget/material[10] - 虽然widget提供功能,但widget/material定义了一个主题。请注意,该界面实际上分为两部分:

    这是为了提高小部件的可重用性和灵活性。我们稍后会用到它。

    默认看起来不错,也是我们将使用的,但通过设置颜色、文本大小字体属性等属性也很容易调整。

    • 注意:Gio 在名为 gio-x[11] 的专用存储库中扩展了基本功能,其中正在开发更多材质组件[12],包括导航栏和工具提示。
  1. 具有状态的实际小部件
  2. 小部件的绘制,完全无状态

04 功能实现

看看本文开头提到的按钮功能如何实现,代码如下:

func main() {
 go func() {
  // 创建一个新窗口
  w := app.NewWindow(
   app.Title("煮蛋计时器"),
   app.Size(unit.Dp(400), unit.Dp(600)),
  )

  // ops 表示 UI 上的操作
  var ops op.Ops

  // startButton 时候一个可点击的小部件
  var startButton widget.Clickable

  // th 定义 material design(材料设计)的风格
  th := material.NewTheme(gofont.Collection())

  // 循环监听窗口上的事件
  for e := range w.Events() {

   // 监听事件的类型
   switch e := e.(type) {

   // 当应用程序需要重新渲染是发送该事件
   case system.FrameEvent:
    gtx := layout.NewContext(&ops, e)
    btn := material.Button(th, &startButton, "Start")
    btn.Layout(gtx)
    e.Frame(gtx.Ops)
   }
  }
 }()
 app.Main()
}

05 代码详解

主要关注这次新增的内容。

1)设置了三个新变量

  • ops 从用户界面定义操作,具体的作用说明,介绍 op 包时讲解了。
  • startButton 是我们的按钮,一个可点击的小部件。
  • th 是材料设计主题,设置字体为 gofonts。

2)事件循环代码:

for e:= range w.Events()

是重点:

  • w.Events() 方法的返回值类型是:<-chan event.Event,为我们提供了传递事件的 channel。我们只是循环监听。
  • event.Event 是一个接口。因此,在循环里面,我们需要进行断言,对不同的事件做不同的响应。
  • 在这个例子中,我们只对事件 system.FrameEvent 进行处理。具体处理逻辑:
    • 请注意,我们如何轻松获得所有鼠标悬停和点击动画。它们都是主题(theme)的一部分,很赞。
    • 我们定义一个新的图形上下文gtx。它接收指向 ops 的指针以及事件 system.FrameEvent
    • btn 是实际的按钮,material.Button 接收主题 th 和指向 startButton 小部件的指针。我们还定义了显示的文本(注意,文本是纯粹显示在按钮上的,而不是按钮这个有状态小部件的一部分。)
    • btn.Layout(gtx) 表示按钮 btn 要求在上下文 gtx 中显示自己。这是关键。布局不布局按钮,按钮自己布局。这非常方便,例如尝试调整窗口大小。无论画布的大小或形状如何,按钮都会重新调整自己。
    • 最终,我们实际将操作 ops 从上下文 gtx 发送到 FrameEvent e

最后,记得实际动手运行看看。我们并没有设置实际的按钮点击样式,Gio 自动帮我们做了。

06 小结

本节虽然只是实现一个按钮,但涉及到的知识点不少,关键代码就几行,但需要花些时间了解下相关 API 的用法,掌握使用 Gio 实现 GUI 的关键步骤和思路。

参考:https://jonegil.github.io/gui-with-gio/egg_timer/03_button.html。

参考资料

[1]

Material Design: https://material.io/

[2]

font/gofont: https://pkg.go.dev/gioui.org/font/gofont

[3]

io/system: https://pkg.go.dev/gioui.org/io/system

[4]

layout: https://pkg.go.dev/gioui.org/layout

[5]

Flexbox: https://pkg.go.dev/gioui.org/layout#Flex

[6]

Mozilla 上的介绍: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox

[7]

op: https://pkg.go.dev/gioui.org/op

[8]

宏: https://pkg.go.dev/gioui.org/op#MacroOp

[9]

widget: https://pkg.go.dev/gioui.org/widget

[10]

widget/material: https://pkg.go.dev/gioui.org/widget/material

[11]

gio-x: https://pkg.go.dev/gioui.org/x

[12]

更多材质组件: https://pkg.go.dev/gioui.org/x/component




往期推荐


欢迎关注「幽鬼」,像她一样做团队的核心。



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存